#!/usr/bin/env python3
'''
Basic IPv4 router (static routing) in Python.
'''

import sys
import os
import time
from switchyard.lib.userlib import *
from collections import deque


class ARP_request:
    def __init__(self, targetprotoaddr, senderhwaddr, senderprotoaddr,
                 port_name, pkt):
        self.num_tries = 0
        self.try_time = time.time()
        self.ipaddr = targetprotoaddr
        self.senderprotoaddr = senderprotoaddr
        self.senderhwaddr = senderhwaddr
        self.port_name = port_name
        self.pkt_queue = deque()
        self.pkt_queue.append(pkt)


class Router(object):
    def create_forwarding_table(self):
        log_info("1")
        ft = []
        # TODO: get table values from net.interfaces()
        for intf in self.net.interfaces():
            prefix = IPv4Address(int(intf.ipaddr) & int(intf.netmask))
            ft.append([str(prefix), str(intf.netmask), None, intf.name])

        # TODO: get table values from file
        try:
            f = open("forwarding_table.txt")
            for line in f:
                entry = line.split(" ")
                entry[3] = entry[3].strip('\n')
                ft.append(entry)
            f.close()
        except FileNotFoundError:
            log_debug("File not found")
        return ft

    def __init__(self, net):
        self.net = net
        self.arp_table = {}  #ip -> mac
        self.router_interfaces = {}
        self.arp_queue = deque()  #queue to keep track of arp requests sent out
        self.forwarding_table = self.create_forwarding_table()
        self.intf_name = {}
        for intf in net.interfaces():
            self.router_interfaces[intf.name] = intf.ipaddr
            self.intf_name[intf.name] = intf

    def create_arp_request(self, matched_entry, targetprotoaddr, pkt):
        log_info("2")
        interface = self.net.interface_by_name(matched_entry[3])
        request_pkt = create_ip_arp_request(interface.ethaddr,
                                            interface.ipaddr, targetprotoaddr)
        senderprotoaddr = interface.ipaddr
        new_request = ARP_request(targetprotoaddr, interface.ethaddr,
                                  senderprotoaddr, interface.name, pkt)
        self.arp_queue.append(new_request)
        self.net.send_packet(interface, request_pkt)
        new_request.num_tries += 1

    def match_forwarding_table(self, dest):
        log_info("3")
        maxprefix = 0
        matched_entry = None
        for entry in self.forwarding_table:
            prefixnet = IPv4Network(entry[0] + '/' + entry[1])
            matches = dest in prefixnet

            # TODO: 如果匹配，则尝试找到最长的前缀
            if matches:
                netaddr = IPv4Network(entry[0] + '/' + entry[1])
                if netaddr.prefixlen >= maxprefix:
                    maxprefix = netaddr.prefixlen
                    matched_entry = entry
        return matched_entry

    def handle_arp_requests(self):
        log_info("4")
        new_queue = deque()
        for request in self.arp_queue:
            if time.time() - request.try_time >= 1:
                if request.num_tries < 5:
                    new_req = create_ip_arp_request(request.senderhwaddr,
                                                    request.senderprotoaddr,
                                                    request.ipaddr)
                    self.net.send_packet(
                        self.net.interface_by_name(request.port_name), new_req)
                    request.num_tries += 1
                    new_queue.append(request)

                # TODO: 已经尝试了5次，删除
                else:
                    del request
            else:
                new_queue.append(request)
        # TODO: 用临时队列更新原队列
        self.arp_queue = new_queue

    def prinf_arp_table(self):
        # TODO: print arp_table
        for key, value in self.arp_table.items():
            log_info('{key}: {value}'.format(key=key, value=value))
        log_info("\n")

    def send_packet(self, pkt, dev, nextMac):
        log_info("12")
        log_info("sending a packet from {} to {}".format(
            pkt[IPv4].src, pkt[IPv4].dst))
        # TODO: change Ethernet header: src is router interface, dst is next hop (not dst)
        pkt[Ethernet].src = self.intf_name[dev].ethaddr
        pkt[Ethernet].dst = nextMac
        log_info("Packet to send:")
        log_info(pkt)
        self.net.send_packet(dev, pkt)

    def router_main(self):
        '''
        Main method for router; we stay in a loop in this method, receiving
        packets until the end of time.
        '''
        while True:
            gotpkt = True
            try:
                timestamp, dev, pkt = self.net.recv_packet(timeout=1.0)
            except NoPackets:
                log_debug("No packets available in recv_packet")
                gotpkt = False
            except Shutdown:
                log_debug("Got shutdown signal")
                break

            if gotpkt:
                log_debug("Got a packet: {}".format(str(pkt)))
                # TODO: get APR packet
                if (pkt.has_header(Arp)):
                    arp = pkt.get_header(Arp)

                    # TODO: get ARP request
                    if arp.operation == 1:
                        log_info("5")
                        # TODO: add request information to arp table
                        if str(arp.senderprotoaddr) not in self.arp_table.keys(
                        ):
                            self.arp_table[str(arp.senderprotoaddr)] = str(
                                arp.senderhwaddr)

                        # TODO: APR reply is dst in router interface
                        if arp.targetprotoaddr in self.router_interfaces.values(
                        ):
                            reply = create_ip_arp_reply(
                                self.net.interface_by_ipaddr(
                                    arp.targetprotoaddr).ethaddr,
                                arp.senderhwaddr, arp.targetprotoaddr,
                                arp.senderprotoaddr)
                            self.net.send_packet(
                                self.net.interface_by_name(dev), reply)
                            continue

                    # TODO: get ARP reply
                    else:
                        log_info("6")
                        self.arp_table[str(arp.senderprotoaddr)] = str(arp.senderhwaddr)
                        # TODO: 从队列中移除对应的ARP request信息，并发送信息
                        resolved = None
                        for request in self.arp_queue:
                            if str(request.ipaddr) in self.arp_table.keys():
                                for packet in request.pkt_queue:
                                    if packet.has_header(Ethernet):
                                        i = packet.get_header_index(
                                            Ethernet)
                                        del packet[i]
                                    # TODO: 提取reply包中的信息补全原包的信息并发送
                                    e = Ethernet()
                                    e.dst = self.arp_table[str(request.ipaddr)]
                                    e.src = self.net.interface_by_name(
                                        request.port_name).ethaddr
                                    packet.prepend_header(e)
                                    self.net.send_packet(
                                        self.net.interface_by_name(
                                            request.port_name), packet)
                                resolved = request
                        if resolved is not None:
                            log_info("7")
                            self.arp_queue.remove(resolved)

                # TODO: get IP packet
                elif (pkt.has_header(IPv4)):
                    ipv4 = pkt.get_header(IPv4)
                    dest = ipv4.dst
                    matched_entry = self.match_forwarding_table(dest)
                    log_info("8")
                    if matched_entry is not None:
                        ipv4.ttl -= 1  # TTL减1
                        if ipv4.ttl > 0:
                            nextIP = matched_entry[2]
                            nextDev = matched_entry[3]

                            # TODO: 特殊情况1，包是路由器本身的
                            if dest in self.router_interfaces.values():
                                pass
                            else:
                                log_info("12")
                                log_info("nextIp: {}".format(nextIP))
                                log_info("dest: {}".format(dest))
                                self.prinf_arp_table()
                                if nextIP is None:
                                    log_info("13")
                                    nextIP = dest
                                    log_info("nextIp: {}".format(nextIP))
                                if str(nextIP) in self.arp_table:
                                    log_info("9")
                                    self.send_packet(
                                        pkt, nextDev,
                                        self.arp_table[str(nextIP)])
                                    log_info("14")
                                else:  # TODO: 不在arp_table中
                                    log_info("11")
                                    in_queue = False
                                    for request in self.arp_queue:
                                        if (request.ipaddr == dest):
                                            request.pkt_queue.append(pkt)
                                            in_queue = True

                                    if not in_queue:
                                        self.create_arp_request(
                                            matched_entry, nextIP, pkt)

                    else:  # TODO: 特殊情况2，表中没有匹配
                        pass

            self.handle_arp_requests()


def main(net):
    '''
    Main entry point for router.  Just create Router
    object and get it going.
    '''
    r = Router(net)
    r.router_main()
    net.shutdown()
